/*!
\mainpage The mainpage for MemAlloc interface by Oleg

ATTENTION, C++11

Please, look at this documentation in generated html or latex files.

Several words about how to implement this interface.

First of all, why didn't I write usual interface with virtual functions?
Answer is quite simple: MemoryAllocator uses template methods but template
methods are not allowed to be virtual in C++. 

So the only way to separate interface from realization is to use one special
pointer in private section named $(CLASS_NAME)Impl and pass concreate 
realization class via constructor. In the interface file we use 
forward-declaration to declare Implementation classes so interfaces file is 
self-sufficient to include it into every project part. As you can see, 
our goal is reached. This file contains only interfaces without realization.
This method is widely-used and called pimpl-idiom (http://www.gotw.ca/gotw/024.htm,
http://www.gotw.ca/gotw/028.htm).
And now several words about how realization file should be structured.
One should write realization for all classes and their $(CLASS_NAME)Impl parts.

MemAlloc is a singleton. It's your care how to implement singleton but method Init 
should construct it and static method Deinit - destroy. When MemAlloc object is
constructed one should use method Instance to get refference for it. 
Constructor of MemAlloc is private because it's supposed to be called only from
 Init function. And small attention. BE VERY VERY CAREFULL WHEN USING SMP
SINGLETON. (I advice you to read this small article 
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf)

And now about SmartPtr. As you can see it has public constructor which
takes pointer to SmartPtrImpl class. But it's critical for us to allow
a SmartPtr construction only for Memory Allocator. But we take it automatically
because nobody has declaration of SmartPtrImpl. Logically SmartPtrImpl and 
MemAllocImpl are two parts of the same mechanise called Memory allocator so 
they should be friends of each other. Thus only MemAllocIMpl will be able to 
construct SmartPointers.

And finally about exception handling policy.
I decided to handle all errors using exceptions. Thus dynamic memory errors
will throw std::bad_alloc exception, all problems with swap file will throw
std::ios_base::failure exceptions. Stop. One moment If you want ofstream behave
this way you should manually multiset it's exception policy flags
 (http://www.cplusplus.com/reference/ios/ios/exceptions/).

Thank you for your reading. I will be happy to answer all your questions and
listen to your proposals.

Oleg <onponomarev@gmail.com>

*/

#ifndef MEMALLOCATOR_HPP_
#define MEMALLOCATOR_HPP_

#include <exception>
#include <string>
#include "my_assert.h"

class MemAllocImpl;
class SmartPtrImpl;
template <class T> class SmartPtr;

//! Memory Allocator interface class
/*! To use MemAlloc class for memory management you should first call Init method.
    Finaly you should call Deinit method. To allocate some memory you should use
    Alloc call and next play a lot with SmartPtr's. When you suppose that you don't
    need memory, please call Free method. Please, look for SmartPtr documentation 
    for more information. */
class MemAlloc {
	template <class T>
	friend class SmartPtr;
	public :

	
	/*! Init function. This function must be called before the first call
	    to Alloc; Please, don't forget to call Deinit after Init
	*/
	/*!
	  \param mem_size RAM limit for allocator
	  \param swap size for allocaor
	  \param swap_path file path and name

	  \throw std::bad_alloc exception on out-of-memory
          \throw std::ios_base::failure for file error
 	  \throw MemAllocException 
	*/
	static void Init(uint64_t mem_size, uint64_t swap_size, 
	    const std::string& swap_path);

	//! Instance function returns correct MemAlloc& with valid pimpl 
	/*!
 	  \throw MemAllocException 
	*/
	static MemAlloc& Instance() {
		my_assert(pinstance_);
		return *pinstance_;
	}


	//! Deinitialize MemAlloc. 
	/*
 	  \throw MemAllocException 
	*/
	static void Deinit();
	
	//! Memory Allocate function.
	/*!
	   Call to this function allocates arrey of elem_count elements of 
	   type T and returns SmartPtr<T> to acess allocated memory.
	   Default array size is 1.
	*/
	/*
	  \tparam T some concreate integer

	  \param elem_count elements count in array we want to allocate.
	  \return SmartPtr<T> which should be used to access memory

	  \throw std::bad_alloc exception on out-of-memory
          \throw std::ios_base::failure for file error
 	  \throw MemAllocException 
	*/
	template <class T>
	SmartPtr<T> Alloc(uint64_t elem_count = 1);

	//! Memory Free function.
	/*!
		Please be carefull with this function to prevent double-free.
	*/
	/*
	  \param T some concreate type

	  \param ptr SmartPtr pointed to memory we want to free

	  \throw std::bad_alloc exception on out-of-memory
          \throw std::ios_base::failure for file error
 	  \throw MemAllocException 
	*/
	template <class T>
	void Free(SmartPtr<T>& ptr);
	
	private :

	/*! Private constuctor getting private forward-declared class pointer with 
	    allocator implementation. It'll be called by Init method.
	*/
	MemAlloc(MemAllocImpl *pimpl);
	
	//! Deleted default constructor to preserve singleton properties
	MemAlloc() = delete;
	//! Deleted copy constructor to preserve singleton properties
	MemAlloc(const MemAlloc&) = delete;
	//! Deleted assign operator to preserve singleton properties
	MemAlloc& operator=(const MemAlloc&) = delete;

	//! MemAlloc private destructor
	~MemAlloc();

	static MemAlloc *pinstance_;
	
	//! Pointer to MemAllocImpl - MemAlloc interface implementation
	MemAllocImpl *pimpl_;
};

template <class T>
class SmartPtr;

//! PtrAssistDeref class
/*! This is utility class which is expected to be returned as a temporary object
    by SmartPtr operator*. It's goal is to automatically Get and Release pointer
    during dereference operation. You should not assign object of this class to
    another objects or save it into variable because pointer will be locked 
    during PtrAssistDeref lifetime. If you want */ 

template <class T>
class PtrAssistDeref {
        public:

        PtrAssistDeref(SmartPtr<T> *ptr, uint64_t idx = 0);
        ~PtrAssistDeref();
	
	/*! It's supposed to be used like this:
		int x = *ptr; 
	    and only in theese cases */
        operator T();
	/*! It's supposed to be used like this:
		*ptr = 5;
	    and only in theese cases */
        PtrAssistDeref<T>& operator= (const T& val);

        private:
	
	PtrAssistDeref(const PtrAssistDeref<T>&) = delete;

        SmartPtr<T> *smart_ptr_;
	const T *ptr_;
	uint64_t i_;
};

//! const copy of PtrAssistDeref class
template <class T>
class ConstPtrAssistDeref {
        public:

        ConstPtrAssistDeref(const SmartPtr<T> *ptr, uint64_t idx = 0);
        ~ConstPtrAssistDeref();
	
        operator T() const;

        private:

	ConstPtrAssistDeref(const PtrAssistDeref<T>&) = delete;
        const SmartPtr<T> *smart_ptr_;
	const T *ptr_;
	uint64_t i_;
};


//! PtrAssistArrow class
/*! This is utility class and it have the same goal and behavior as PtrAssistDeref */ 
template <class T>
class PtrAssistArrow {
        public:

        PtrAssistArrow(SmartPtr<T> *ptr); 
        ~PtrAssistArrow(); 
	
	/*! It's supposed to be used like this:
		ptr->a = 5;
		int x = ptr->a;
	    and only in theese cases */
        T* operator-> ();

        private:

	PtrAssistArrow(const PtrAssistArrow<T>&) = delete;

        SmartPtr<T> *smart_ptr_;
	const T *ptr_;
};

//! const copy of PtrAssistArrow class
template <class T>
class ConstPtrAssistArrow {
        public:

        ConstPtrAssistArrow(const SmartPtr<T> *ptr); 
        ~ConstPtrAssistArrow(); 
	
        const T* operator-> ();

        private:

	ConstPtrAssistArrow(const ConstPtrAssistArrow<T>&) = delete;

        const SmartPtr<T> *smart_ptr_;
	const T *ptr_;
};


//! SMartPtr interface class
/*! Object of this class is returned by Allocate in MemAlloc. One should use
    SmartPtr's instead of usual C pointers everywhere in his code. This will 
    allow MemAlloc to know when memory is actively used and when it may be 
    unloaded to a swap area.
*/ 
template <class T> 
class SmartPtr {
	friend class MemAlloc;
	public:
	
	//! Explicitely delete default constructor 
	SmartPtr() = delete;

	/*! Constructor getting implementation pointer is only way to
	    construct SmartPtr
	    
              \param pimpl pointer to a realization

	      \throw std::bad_alloc exception on out-of-memory
	
	*/
	SmartPtr(SmartPtrImpl *pimpl);

	/*! Simple copy constructor for reference counter
	      \throw std::bad_alloc exception on out-of-memory
	*/
	SmartPtr(const SmartPtr<T>& ptr);
	//! Simple destructor for reference counter

	~SmartPtr();

	/*! Simple operator = for reference counter
	      \throw std::bad_alloc exception on out-of-memory
	*/
	SmartPtr& operator=(const SmartPtr& ptr);


	///@{
	/*! Usual smart-pointer access operators. Access may take a lot of time 
	    because of possible swapping. Theese functions may throw!

	  \throw std::bad_alloc exception on out-of-memory
          \throw std::ios_base::failure for file error
 	  \throw MemAllocException 
	*/	
	PtrAssistDeref<T> operator*();
	ConstPtrAssistDeref<T> operator*() const;
	PtrAssistArrow<T> operator->();
	ConstPtrAssistArrow<T> operator->() const;

	PtrAssistDeref<T> operator[](uint64_t idx);
	ConstPtrAssistDeref<T> operator[](uint64_t idx) const;
	///@}

	/*! Using Get/Release methods one can get usual C pointer from SmartPtr and lock
	    memory for swapping until Release method called

	  \throw std::bad_alloc exception on out-of-memory
          \throw std::ios_base::failure for file error
 	  \throw MemAllocException 
	*/
	///@{
	T* Get();
	void Release();
	///@}

	private:

	SmartPtrImpl *pimpl_;
//	T *ptr_;
};

//! General exception class for MemAlloc
class MemAllocException : public std::exception {
	public:

	/*! An ErrorCode enum contains several constants which may be used in
	    MemAllocException and derived exception classes */
	enum ErrorCode { 
		E_NOMEM, /*!< Not enough memory to load memory region from swap */
		E_NOBUFS, /*!< Too fragmented RAM */
		E_2BIG, /*!< Requested memory region is too big for allocator */
		E_MLINK, /*!< Lack of free memory identifiers */
		E_ALREADY, /*!< Allocator has already been initialized */
		E_UNINITIALIZED, /*!< Allocator is unitialized */
		E_INVALID_INIT_SIZES, /*!< Swap area is smaller than mem area */
		E_ACTIVE_MEMORY_FREE, /*!< Someone tried to Free memory which 
		                          is currently in use */
		E_OUT_OF_BOUNDS /*!< array index is out of bounds */
	};


	//! No default constructor
	MemAllocException() = delete;
	//! Only constructor taking error_code and error_message 
	MemAllocException(ErrorCode error_code, const std::string& error_msg);
	//! Virtual destructor 
	virtual ~MemAllocException() noexcept;

	//! Standart std::exception what redefinition() 
	virtual const char *what() const noexcept;
	//! GetErrorCode method returns ErrorCode
	virtual ErrorCode GetErrorCode() const noexcept; 
};

/*! Exception class for MemAlloc which is supposed to be used for fixable errors.
    For example sometimes it's enough simply to wait for the empty memory in RAM.
    This exception class is a convenient way to catch those exceptions ant try again.
*/
class MemAllocWaitException : public MemAllocException {
	public:

	//! Only constructor taking error_code and error_message 
	MemAllocWaitException(ErrorCode error_code, const std::string& error_msg);
	//! Virtual destructor 
	virtual ~MemAllocWaitException() noexcept;
};

#endif //MEMALLOCATOR_HPP_
